/*
* Copyright (c) 2007 by Kirill Kolodyazhniy.
* See the file "license.terms" for information on usage and redistribution.
*/

#include "shadowvolume.h"
#include "light.h"
#include "resourcemanager.h"
#include "graphic.h"

namespace Engine
{
	using namespace std;
	/*******************************************************************************/
	ShadowVolume::ShadowVolume()
		:m_extrudeDistance(1000)
	{
		WeakUtils::UnsafeCopy(m_material, ResourceManager::GetSingleton().Create("shadow.material", "Material"));
		m_material->Load();

		m_vertexDeclaration = Graphic::GetSingleton().GetRenderSystem()->CreateVertexDeclaration();
		m_vertexDeclaration->AddElement(0, 0, VET_FLOAT3, VES_POSITION);
	}
	/*******************************************************************************/
	ShadowVolume::~ShadowVolume()
	{
		if(!m_material.IsNull())
		{
			std::string name = m_material->GetName();
			m_material.Delete();
			ResourceManager::GetSingleton().TryRemove(name);
		}
	}
	/*******************************************************************************/
	struct Vertex
	{
		Vertex(float x,float y,float z):m_x(x),m_y(y),m_z(z){}
		Vertex(Vector3<REAL> vec):m_x(static_cast<float>(vec.x)),m_y(static_cast<float>(vec.y)),m_z(static_cast<float>(vec.z)){}
		float m_x;
		float m_y;
		float m_z;
	};
	void ShadowVolume::Update(const Light* light, const SubEntityVector* rend)
	{
		SubEntityVector::const_iterator r = rend->begin(), re = rend->end();

		if(m_lightPos != light->GetPosition() || m_transform != (*rend->begin())->GetWorldTransform())
		{
			m_lightPos = light->GetPosition();
			m_transform = (*rend->begin())->GetWorldTransform();
			Vector3<REAL> trueLightPos = m_transform.Inverse() * m_lightPos;

			vector<EdgeElem> edges;

			std::vector<Vertex> capsVertices;

			for(;r != re; ++r)
			{
				if((*r)->GetMaterial()->IsTransparent())
					continue;

				unsigned int faces = (*r)->GetRenderData()->GetPrimitiveCount();
				edges.reserve(edges.size()/2 + faces * 6);

				const HardWareBufferPtr vetices = (*r)->GetVertexData();
				const HardWareBufferPtr indices = (*r)->GetIndexData();

				size_t vertexSize = (*r)->GetVertexDeclaration()->GetVertexSize(0);

				//find position
				size_t positionPos = 0;
				const VertexElement* velem = (*r)->GetVertexDeclaration()->FindElementBySemantic(VES_POSITION,0);
				if(velem != 0)
				{
					if(velem->GetType() == VET_FLOAT3)
						positionPos = velem->GetOffset();
					else
						throw std::logic_error("Vertex data has position element with wrong type for shadow.");
				}
				else
					throw std::logic_error("Vertex data has no position element for shadow.");

				const void* vertexDataPtr = vetices->Lock();

				const unsigned short* indexDataPtr = reinterpret_cast<const unsigned short*>(indices->Lock());

				for(unsigned int f = 0; f < faces; ++f)
				{
					unsigned short vertex0 = indexDataPtr[3*f+0];
					unsigned short vertex1 = indexDataPtr[3*f+1];
					unsigned short vertex2 = indexDataPtr[3*f+2];

					float coord[3];
					memcpy(coord,(reinterpret_cast<const unsigned char*>(vertexDataPtr) + vertex0 * vertexSize + positionPos),sizeof(coord));
					Vector3<REAL> v0(coord[0],coord[1],coord[2]);
					memcpy(coord,(reinterpret_cast<const unsigned char*>(vertexDataPtr) + vertex1 * vertexSize + positionPos),sizeof(coord));
					Vector3<REAL> v1(coord[0],coord[1],coord[2]);
					memcpy(coord,(reinterpret_cast<const unsigned char*>(vertexDataPtr) + vertex2 * vertexSize + positionPos),sizeof(coord));
					Vector3<REAL> v2(coord[0],coord[1],coord[2]);

					// Transform vertices or transform light?
					Vector3<REAL> vCross1(v2-v1);
					Vector3<REAL> vCross2(v1-v0);
					Vector3<REAL> vNormal;
					vNormal = vCross1.CrossProduct(vCross2);
					vNormal.Normalise();

					if( vNormal.DotProduct(trueLightPos) >= 0.0 )
					{
						AddEdge( edges, EdgeElem(vertex0,v0), EdgeElem(vertex1,v1));
						AddEdge( edges, EdgeElem(vertex1,v1), EdgeElem(vertex2,v2));
						AddEdge( edges, EdgeElem(vertex2,v2), EdgeElem(vertex0,v0));
						//add the light back facing triangles to the Shadow volume to form the front cap
						//capsVertices.push_back(v2);
						//capsVertices.push_back(v1);
						//capsVertices.push_back(v0);
						//Reverse the normals for light back facing triangles and extend them in the light direction to form the back cap of the shadow volume
						//capsVertices.push_back(v0 - trueLightPos * m_extrudeDistance);
						//capsVertices.push_back(v1 - trueLightPos * m_extrudeDistance);
						//capsVertices.push_back(v2 - trueLightPos * m_extrudeDistance);
					}
				}
				vetices->UnLock();
				indices->UnLock();
			}

			if(edges.empty())
				return;

			// create render objects
			m_indexData = Graphic::GetSingleton().GetRenderSystem()->CreateIndexBuffer(edges.size()/2 * 6 + capsVertices.size(), INDEX_BUFFER_16);

			m_vertexData = Graphic::GetSingleton().GetRenderSystem()->CreateVertexBuffer(edges.size()/2 * 6 + capsVertices.size(), m_vertexDeclaration->GetVertexSize(0));

			unsigned short* newIndexDataPtr = reinterpret_cast<unsigned short*>(m_indexData->Lock());

			Vertex* newVertexDataPtr = static_cast<Vertex*>(m_vertexData->Lock());
			size_t vertexIndex = 0;
			unsigned short indexIndex = 0;

			//fill caps render objects
			for(size_t  c = 0; c < capsVertices.size(); ++c )
			{
				newVertexDataPtr[vertexIndex++] = capsVertices[c];
				newIndexDataPtr[indexIndex] = indexIndex;indexIndex++;
			}

			//fill render objects
			for(size_t  e = 0; e < edges.size()/2; ++e )
			{
				Vector3<REAL> v1(edges[2 * e].vertex);
				Vector3<REAL> v2(edges[2 * e + 1].vertex);
				Vector3<REAL> v3 = v1 - trueLightPos * m_extrudeDistance;
				Vector3<REAL> v4 = v2 - trueLightPos * m_extrudeDistance;

				// Add a quad (two triangles) to the vertex list
				newVertexDataPtr[vertexIndex++] = Vertex(v1);
				newIndexDataPtr[indexIndex] = indexIndex;indexIndex++;
				newVertexDataPtr[vertexIndex++] = Vertex(v2);
				newIndexDataPtr[indexIndex] = indexIndex;indexIndex++;
				newVertexDataPtr[vertexIndex++] = Vertex(v3);
				newIndexDataPtr[indexIndex] = indexIndex;indexIndex++;

				newVertexDataPtr[vertexIndex++] = Vertex(v2);
				newIndexDataPtr[indexIndex] = indexIndex;indexIndex++;
				newVertexDataPtr[vertexIndex++] = Vertex(v4);
				newIndexDataPtr[indexIndex] = indexIndex;indexIndex++;
				newVertexDataPtr[vertexIndex++] = Vertex(v3);
				newIndexDataPtr[indexIndex] = indexIndex;indexIndex++;
			}

			m_indexData->UnLock();
			m_vertexData->UnLock();

			m_renderData = Graphic::GetSingleton().GetRenderSystem()->CreateRenderData(edges.size()/2 * 2 + capsVertices.size(), edges.size()/2 * 6 + capsVertices.size(),m_vertexDeclaration,m_indexData,m_vertexData);
		}
	}
	/*******************************************************************************/
	void ShadowVolume::AddEdge( vector<EdgeElem>& edges, EdgeElem v0, EdgeElem v1 )
	{
		// Remove interior edges (which appear in the list twice)
		for( size_t i = 0; i < edges.size()/2; ++i )
		{
			if( ( edges[2*i+0] == v0 && edges[2*i+1] == v1 ) ||
				( edges[2*i+0] == v1 && edges[2*i+1] == v0 ) )
			{
				edges.erase(edges.begin() + 2*i,edges.begin() + 2*i + 2);
				return;
			}
		}

		edges.push_back(v0);
		edges.push_back(v1);
	}
	/*******************************************************************************/
	const MaterialPtr ShadowVolume::GetMaterial(void) const
	{
		return m_material;
	}
	/*******************************************************************************/
	RenderDataPtr ShadowVolume::GetRenderData() const
	{
		return m_renderData;
	}
	/*******************************************************************************/
	const Matrix4<REAL>& ShadowVolume::GetWorldTransform() const
	{
		return m_transform;
	}
	/*******************************************************************************/
	const HardWareBufferPtr ShadowVolume::GetVertexData() const
	{
		return m_vertexData;
	}
	/*******************************************************************************/
	const HardWareBufferPtr ShadowVolume::GetIndexData() const
	{
		return m_indexData;
	}
	/*******************************************************************************/
	const VertexDeclarationPtr ShadowVolume::GetVertexDeclaration() const
	{
		return m_vertexDeclaration;
	}
	/*******************************************************************************/
}
